<?php
/**
 ***********************************************************************************************************************
 * Connection Proxy 注：本类依赖于app/config/db.php的配置文件
 * @author llq
 ***********************************************************************************************************************
 */
namespace artisan\db;
use artisan\config;

/**
 * Class MySQLiProxy
 * @package artisan\db
 */
class MySQLiProxy
{
    /**
     * @var array 服务器信息
     */
    private static $servers = [];

    /**
     * @var array 已存在的连接
     */
    private static $connections = [];

    /**
     * 关闭当前连接
     */
    public static function close($alias)
    {
        if(empty($alias)) {
            return false;
        }
        if(self::getMaster($alias)) {
            self::getMaster($alias)->close();
            unset(self::$connections['master'][$alias]);
        }
        if(self::getSlave($alias)) {
            self::getSlave($alias)->close();
            unset(self::$connections['slave'][$alias]);
        }
        return true;
    }

    /**
     * 获取一个可用连接
     *
     * @param $alias
     * @param null|bool $flag 连接方式：null，智能选择。true，使用主库。false，使用从库。
     * @param string $sql 待分析的sql
     * @return MySQLiDriver|bool
     */
    public static function get($alias, $flag = null, $sql = '')
    {
        $alias = (string)$alias;
        self::$servers = self::$servers ?: config::get('db');
        if(
            empty(self::$servers[$alias]) ||
            empty(self::$servers[$alias]['db'])
        ) {
            return false;
        }

        // ① 主库连接存在，使用主库
        if(!empty(self::$connections['master'][$alias])) {
            return self::$connections['master'][$alias];
        }
        // ② 强制选择主库，使用主库
        if($flag === true) {
            return self::getMaster($alias);
        }

        // ③ 强制选择从库，使用从库
        // ④ SQL是读操作，使用从库
        if(
            $flag === false ||
            preg_match('/^SELECT|SHOW|DESCRIBE|EXPLAIN/', strtoupper(trim(preg_replace('/\s+/', ' ', (string)$sql))))
        ) {
            return self::getSlave($alias);
        }

        // ⑤ 默认使用主库
        return self::getMaster($alias);
    }

    /**
     * 获取一个主库的连接
     *
     * @param string $alias
     * @return MySQLiDriver|bool
     */
    private static function getMaster($alias)
    {
        if($connection = self::realConnect(self::$servers[$alias])) {
            self::$connections['master'][$alias] = $connection;
            return $connection;
        }
        return false;
    }

    /**
     * 获取一个从库连接
     *
     * @param string $alias
     * @return MySQLiDriver|bool
     */
    private static function getSlave($alias)
    {
        if(!empty(self::$connections['slave'][$alias])) {
            return self::$connections['slave'][$alias];
        }

        if(
            empty(self::$servers[$alias]['slaves']) ||
            !$slave_server = self::poll(self::$servers[$alias]['slaves'])
        ) {
            return self::getMaster($alias);
        }

        if($connection = self::realConnect($slave_server)) {
            self::$connections['slave'][$alias] = $connection;
            return $connection;
        }

        return false;
    }

    /**
     * 根据权重轮询选择一个可用从库
     *
     * @param array $servers
     * @return bool
     */
    private static function poll(array $servers)
    {
        if(empty($servers)) {
            return false;
        }

        $count = 0;
        $slaves = array();
        foreach($servers as $slave) {
            if(!isset($slave['enable']) || $slave['enable'] == true) {
                $slave['weight'] = empty($slave['weight']) ? 100 : intval($slave['weight']);
                $slaves[] = $slave;
                $count++;
            }
        }

        if($count == 0) {
            return false;
        }

        $weight_sum = 0;
        foreach($slaves as $val) {
            $weight_sum += $val['weight'];
        }
        $roll = rand(1, $weight_sum);
        $tmp = 0;
        $key = 0;
        foreach($slaves as $slave_key => $slave) {
            $min = $tmp;
            $tmp += $slave['weight'];
            $max = $tmp;
            if ($roll > $min && $roll <= $max) {
                $key = $slave_key;
                break;
            }
        }
        return $slaves[$key];
    }

    /**
     * 真正创建一个数据库连接
     *
     * @param array $server
     * @return MySQLiDriver|bool
     */
    private static function realConnect(array $server)
    {
        $conn = new MySQLiDriver(
            isset($server['host']) ? $server['host'] : '',
            isset($server['user']) ? $server['user'] : '',
            isset($server['pass']) ? $server['pass'] : '',
            isset($server['db']) ? $server['db'] : '',
            isset($server['port']) ? $server['port'] : '',
            isset($server['charset']) ? $server['charset'] : ''
        );
        if(!$conn->connect()) {
            return false;
        }
        return $conn;
    }

}